MediaRecorder APIλ₯Ό μ¬μ©νμ¬ λΈλΌμ°μ κΈ°λ° MediaStream λ Ήνμ μΈκ³λ₯Ό ννν΄ λ³΄μΈμ. μλ² μΈ‘ μμ‘΄μ± μμ΄ λΈλΌμ°μ μμ μ§μ μ€λμ€μ λΉλμ€λ₯Ό μΊ‘μ²νμ¬ νλΆν μΉ μ ν리μΌμ΄μ μ ꡬννλ λ°©λ²μ λ°°μ보μΈμ.
νλ‘ νΈμλ MediaStream λ Ήν: λΈλΌμ°μ κΈ°λ° λ―Έλμ΄ μΊ‘μ²
μΉ λΈλΌμ°μ λ΄μμ μ§μ μ€λμ€μ λΉλμ€λ₯Ό μΊ‘μ²νλ κΈ°λ₯μ μΉ μ ν리μΌμ΄μ
κ°λ°μ νμ μ κ°μ Έμμ΅λλ€. νλ‘ νΈμλ MediaStream λ
Ήνλ MediaRecorder APIλ₯Ό νμ©νμ¬ λ³΅μ‘ν μλ² μΈ‘ μ²λ¦¬ μμ΄ μ΄ κΈ°λ₯μ ꡬνν μ μλ κ°λ ₯νκ³ ν¨μ¨μ μΈ λ°©λ²μ μ 곡ν©λλ€. μ΄ μ κ·Ό λ°©μμ νΉν μ¨λΌμΈ νμ, λΉλμ€ νΈμ§ λꡬ, λνν νν 리μΌκ³Ό κ°μ μ ν리μΌμ΄μ
μμ μ€μκ° μνΈ μμ©, μ§μ° μκ° κ°μ, ν₯μλ μ¬μ©μ κ²½νμ κ°λ₯νκ² ν©λλ€.
MediaStream API μ΄ν΄νκΈ°
λΈλΌμ°μ κΈ°λ° λ―Έλμ΄ μΊ‘μ²μ ν΅μ¬μλ MediaStream APIκ° μμ΅λλ€. MediaStreamμ μ€λμ€λ λΉλμ€ νΈλκ³Ό κ°μ λ―Έλμ΄ λ°μ΄ν°μ μ€νΈλ¦Όμ λνλ
λλ€. MediaStreamμ μ κ·Όνλ €λ©΄ μΌλ°μ μΌλ‘ getUserMedia() λ©μλλ₯Ό μ¬μ©ν©λλ€.
getUserMedia() λ©μλλ μ¬μ©μμκ² λ§μ΄ν¬ λ°/λλ μΉ΄λ©λΌ μ κ·Ό κΆνμ μμ²νλ ν둬ννΈλ₯Ό νμν©λλ€. μ¬μ©μκ° κΆνμ λΆμ¬νλ©΄ MediaStream κ°μ²΄λ‘ resolveλλ Promiseλ₯Ό λ°ννκ³ , μ¬μ©μκ° κΆνμ κ±°λΆνκ±°λ μ κ·Όμ΄ λΆκ°λ₯ν κ²½μ° μ€λ₯μ ν¨κ» rejectλ©λλ€.
μμ : μΉ΄λ©λΌ μ κ·Ό μμ²νκΈ°
μ¬μ©μμ μΉ΄λ©λΌμ λν μ κ·Όμ μμ²νλ κΈ°λ³Έμ μΈ μμ λ λ€μκ³Ό κ°μ΅λλ€:
navigator.mediaDevices.getUserMedia({ video: true, audio: false })
.then(function(stream) {
// Stream is available, do something with it
console.log("Camera access granted!");
})
.catch(function(error) {
console.error("Error accessing camera: ", error);
});
μ€λͺ :
navigator.mediaDevices.getUserMedia({ video: true, audio: false }): μ΄ μ½λλ μΉ΄λ©λΌ(video: true)μ λν μ κ·Όμ μμ²νκ³ μ€λμ€(audio: false)λ λͺ μμ μΌλ‘ λΉνμ±νν©λλ€. μ΄ μ΅μ λ€μ μ‘°μ νμ¬ μ€λμ€μ λΉλμ€ λͺ¨λ λλ μ€λμ€λ§ μμ²ν μ μμ΅λλ€..then(function(stream) { ... }): μ΄ λΈλ‘μ μ¬μ©μκ° κΆνμ λΆμ¬ν κ²½μ° μ€νλ©λλ€.streamλ³μλMediaStreamκ°μ²΄λ₯Ό λ΄κ³ μμ΅λλ€..catch(function(error) { ... }): μ΄ λΈλ‘μ μ¬μ©μκ° κΆνμ κ±°λΆνλ λ± μ€λ₯κ° λ°μνμ λ μ€νλ©λλ€. μ’μ μ¬μ©μ κ²½νμ μ 곡νκΈ° μν΄ μ€λ₯λ₯Ό μ μ νκ² μ²λ¦¬νλ κ²μ΄ μ€μν©λλ€.
getUserMedia()μ κ΅¬μ± μ΅μ
getUserMedia() λ©μλλ λ―Έλμ΄ μ€νΈλ¦Όμ μνλ νΉμ±μ μ§μ ν μ μλ μ νμ μ μ½(constraints) κ°μ²΄λ₯Ό μΈμλ‘ λ°μ΅λλ€. μ¬κΈ°μλ λ€μκ³Ό κ°μ μ΅μ
μ΄ ν¬ν¨λ©λλ€:
video: λΉλμ€λ₯Ό μμ²νλ λΆλ¦¬μΈ κ°(true/false) λλ ν΄μλ, νλ μλ₯ κ³Ό κ°μ λ ꡬ체μ μΈ λΉλμ€ μ μ½μ μν κ°μ²΄.audio: μ€λμ€λ₯Ό μμ²νλ λΆλ¦¬μΈ κ°(true/false) λλ μμ½ μΊμ¬λ§, λ Έμ΄μ¦ μ΅μ μ κ°μ λ ꡬ체μ μΈ μ€λμ€ μ μ½μ μν κ°μ²΄.width: μνλ λΉλμ€ μ€νΈλ¦Όμ λλΉ.height: μνλ λΉλμ€ μ€νΈλ¦Όμ λμ΄.frameRate: μνλ λΉλμ€ μ€νΈλ¦Όμ νλ μλ₯ .
μμ : νΉμ μΉ΄λ©λΌ ν΄μλ μμ²νκΈ°
navigator.mediaDevices.getUserMedia({
video: {
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 480, ideal: 720, max: 1080 }
},
audio: true
})
.then(function(stream) {
// Stream is available
})
.catch(function(error) {
// Handle errors
});
μ΄ μμ μμλ λλΉκ° 640μμ 1920 ν½μ μ¬μ΄(μ΄μμ μΌλ‘λ 1280), λμ΄κ° 480μμ 1080 ν½μ μ¬μ΄(μ΄μμ μΌλ‘λ 720)μΈ λΉλμ€ μ€νΈλ¦Όμ μμ²νκ³ μμ΅λλ€. λν μ€λμ€λ ν¨κ» μμ²ν©λλ€.
MediaRecorder API μκ°
MediaStreamμ μ»μλ€λ©΄, MediaRecorder APIλ₯Ό μ¬μ©νμ¬ λ―Έλμ΄ λ°μ΄ν°λ₯Ό λ
Ήνν μ μμ΅λλ€. MediaRecorder APIλ λ
Ήν μμ, μ€μ§, μΌμ μ€μ§, μ¬κ°λ₯Ό μν λ©μλμ λ
Ήνλ λ°μ΄ν°μ μ κ·Όνλ λ°©λ²μ μ 곡ν©λλ€.
MediaRecorder μΈμ€ν΄μ€ μμ±νκΈ°
MediaRecorder μΈμ€ν΄μ€λ₯Ό μμ±νλ €λ©΄, MediaStream κ°μ²΄λ₯Ό MediaRecorder μμ±μμ μ λ¬ν©λλ€:
const mediaRecorder = new MediaRecorder(stream);
μμ±μμμ λ Ήνλ λ°μ΄ν°μ λν΄ μνλ MIME νμ μ μ§μ νλ λ± μΆκ°μ μΈ μ΅μ μ μ§μ ν μλ μμ΅λλ€:
const options = { mimeType: 'video/webm;codecs=vp9' };
const mediaRecorder = new MediaRecorder(stream, options);
μ§μλλ MIME νμ :
μ¬μ© κ°λ₯ν MIME νμ μ λΈλΌμ°μ μ μ§μνλ μ½λ±μ λ°λΌ λ€λ¦ λλ€. μΌλ°μ μΈ MIME νμ μ λ€μκ³Ό κ°μ΅λλ€:
video/webm;codecs=vp9video/webm;codecs=vp8video/mp4;codecs=avc1audio/webm;codecs=opusaudio/ogg;codecs=vorbis
MediaRecorder.isTypeSupported() λ©μλλ₯Ό μ¬μ©νμ¬ νΉμ MIME νμ
μ΄ λΈλΌμ°μ μμ μ§μλλμ§ νμΈν μ μμ΅λλ€:
if (MediaRecorder.isTypeSupported('video/webm;codecs=vp9')) {
console.log('video/webm;codecs=vp9 is supported');
} else {
console.log('video/webm;codecs=vp9 is not supported');
}
MediaRecorderλ‘ λ°μ΄ν° λ ΉννκΈ°
MediaRecorder APIλ λ
Ήν κ³Όμ μ λͺ¨λν°λ§νκΈ° μν΄ μμ ν μ μλ μ¬λ¬ μ΄λ²€νΈλ₯Ό μ 곡ν©λλ€:
dataavailable: μ μ₯ν λ°μ΄ν°κ° μμ λλ§λ€ λ°μνλ μ΄λ²€νΈμ λλ€.start: λ Ήνκ° μμλ λ λ°μνλ μ΄λ²€νΈμ λλ€.stop: λ Ήνκ° μ€μ§λ λ λ°μνλ μ΄λ²€νΈμ λλ€.pause: λ Ήνκ° μΌμ μ€μ§λ λ λ°μνλ μ΄λ²€νΈμ λλ€.resume: λ Ήνκ° μ¬κ°λ λ λ°μνλ μ΄λ²€νΈμ λλ€.error: λ Ήν μ€ μ€λ₯κ° λ°μνλ©΄ λ°μνλ μ΄λ²€νΈμ λλ€.
κ°μ₯ μ€μν μ΄λ²€νΈλ dataavailableμ
λλ€. μ΄ μ΄λ²€νΈλ λ
Ήνλ λ°μ΄ν°λ₯Ό ν¬ν¨νλ Blob κ°μ²΄λ₯Ό μ 곡ν©λλ€. μ΄λ¬ν Blob κ°μ²΄λ€μ μΆμ ν λ€μ λ
Ήνκ° μλ£λμμ λ λ¨μΌ BlobμΌλ‘ κ²°ν©ν μ μμ΅λλ€.
μμ : λΉλμ€ λ Ήν λ° μ μ₯νκΈ°
let recordedChunks = [];
mediaRecorder.ondataavailable = function(event) {
console.log('data-available: ', event.data.size);
if (event.data.size > 0) {
recordedChunks.push(event.data);
}
};
mediaRecorder.onstop = function() {
console.log('Recording stopped!');
const blob = new Blob(recordedChunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = 'recorded-video.webm';
document.body.appendChild(a);
a.click();
setTimeout(() => {
URL.revokeObjectURL(url);
document.body.removeChild(a);
}, 100);
};
mediaRecorder.start();
console.log("Recording started!");
// To stop recording:
// mediaRecorder.stop();
μ€λͺ :
let recordedChunks = [];: λ Ήνλ λ°μ΄ν° μ²ν¬λ₯Ό μ μ₯νκΈ° μν λ°°μ΄μ λλ€.mediaRecorder.ondataavailable = function(event) { ... }: μ΄ ν¨μλ μλ‘μ΄ λ°μ΄ν°λ₯Ό μ¬μ©ν μ μμ λλ§λ€ νΈμΆλ©λλ€. λ°μ΄ν°λ₯ΌrecordedChunksλ°°μ΄μ μΆκ°ν©λλ€.mediaRecorder.onstop = function() { ... }: μ΄ ν¨μλ λ Ήνκ° μ€μ§λ λ νΈμΆλ©λλ€. μΆμ λ μ²ν¬λ‘λΆν°Blobμ μμ±νκ³ , ν΄λΉBlobμ λν URLμ μμ±νλ©°, λ€μ΄λ‘λ λ§ν¬λ₯Ό λ§λ€μ΄ λ€μ΄λ‘λλ₯Ό νΈλ¦¬κ±°ν©λλ€. λν μ§§μ μ§μ° ν μμ±λ URL κ°μ²΄λ₯Ό μ 리ν©λλ€.mediaRecorder.start();: λ Ήν κ³Όμ μ μμν©λλ€.mediaRecorder.stop();: λ Ήνλ₯Ό μ€μ§νλ €λ©΄ μ΄ λ©μλλ₯Ό νΈμΆν©λλ€.
λ Ήν κ³Όμ μ μ΄νκΈ°
MediaRecorder APIλ λ
Ήν κ³Όμ μ μ μ΄νκΈ° μν λ©μλλ₯Ό μ 곡ν©λλ€:
start(timeslice): λ Ήνλ₯Ό μμν©λλ€. μ νμ μΈμμΈtimesliceλdataavailableμ΄λ²€νΈκ° λ°μν΄μΌ νλ κ°κ²©(λ°λ¦¬μ΄ λ¨μ)μ μ§μ ν©λλ€.timesliceκ° μ 곡λμ§ μμΌλ©΄dataavailableμ΄λ²€νΈλ λ Ήνκ° μ€μ§λ λλ§ λ°μν©λλ€.stop(): λ Ήνλ₯Ό μ€μ§ν©λλ€.pause(): λ Ήνλ₯Ό μΌμ μ€μ§ν©λλ€.resume(): λ Ήνλ₯Ό μ¬κ°ν©λλ€.requestData():dataavailableμ΄λ²€νΈλ₯Ό μλμΌλ‘ νΈλ¦¬κ±°ν©λλ€.
λΈλΌμ°μ νΈνμ± λ° ν΄λ¦¬ν
MediaStream λ° MediaRecorder APIλ μ΅μ λΈλΌμ°μ μμ λ리 μ§μλ©λλ€. κ·Έλ¬λ ꡬν λΈλΌμ°μ λ μ΄λ¬ν APIλ₯Ό κΈ°λ³Έμ μΌλ‘ μ§μνμ§ μμ μ μμ΅λλ€. ꡬν λΈλΌμ°μ λ₯Ό μ§μν΄μΌ νλ κ²½μ° ν΄λ¦¬νμ μ¬μ©νμ¬ νμν κΈ°λ₯μ μ 곡ν μ μμ΅λλ€.
λ€μκ³Ό κ°μ μ¬λ¬ ν΄λ¦¬νμ μ¬μ©ν μ μμ΅λλ€:
adapter.js: μ΄ ν΄λ¦¬νμgetUserMedia()λ₯Ό ν¬ν¨ν WebRTC APIμ λν ν¬λ‘μ€ λΈλΌμ°μ νΈνμ±μ μ 곡ν©λλ€.recorderjs: κΈ°λ³Έμ μΌλ‘MediaRecorderλ₯Ό μ§μνμ§ μλ λΈλΌμ°μ λ₯Ό μν΄ ν΄λΉ κΈ°λ₯μ μ 곡νλ μλ°μ€ν¬λ¦½νΈ λΌμ΄λΈλ¬λ¦¬μ λλ€.
μ€μ©μ μΈ μ μ© λ° μ¬μ© μ¬λ‘
νλ‘ νΈμλ MediaStream λ Ήνλ μΉ μ ν리μΌμ΄μ κ°λ°μ μμ΄ κ΄λ²μν κ°λ₯μ±μ μ΄μ΄μ€λλ€. λ€μμ λͺ κ°μ§ μ€μ©μ μΈ μ μ© λ° μ¬μ© μ¬λ‘μ λλ€:
- μ¨λΌμΈ νμ λ° νμ νμ: μ¨λΌμΈ νμ λ° νμ νμλ₯Ό μν΄ μ€λμ€ λ° λΉλμ€ μ€νΈλ¦Όμ μ€μκ°μΌλ‘ μΊ‘μ²νκ³ μ μ‘ν©λλ€.
- λΉλμ€ νΈμ§ λꡬ: μ¬μ©μκ° λΈλΌμ°μ μμ μ§μ λΉλμ€ μ½ν μΈ λ₯Ό λ Ήννκ³ νΈμ§ν μ μλλ‘ ν©λλ€.
- λνν νν λ¦¬μΌ λ° λ°λͺ¨: μ¬μ©μ μνΈ μμ©μ μΊ‘μ²νκ³ κ°μΈνλ νΌλλ°±μ μ 곡νλ λνν νν λ¦¬μΌ λ° λ°λͺ¨λ₯Ό λ§λλλ€.
- μμ± λ Ήμ μ ν리μΌμ΄μ : λ©λͺ¨ μμ±, μμ± λ©λͺ¨ λ° μ€λμ€ νΈμ§μ μν μμ± λ Ήμ μ ν리μΌμ΄μ μ ꡬμΆν©λλ€.
- κ°μ μμ€ν λ° λ³΄μ μΉ΄λ©λΌ: λΉλμ€ μ€νΈλ¦Όμ μΊ‘μ²νκ³ λ Ήννλ λΈλΌμ°μ κΈ°λ° κ°μ μμ€ν λ° λ³΄μ μΉ΄λ©λΌλ₯Ό ꡬνν©λλ€.
- μ κ·Όμ± λꡬ: μμ±μ λ Ήμνμ¬ μ€μκ°μΌλ‘ ν μ€νΈλ‘ λ³ννκ±°λ, λμ€μ κ²ν ν μ μλλ‘ νλ©΄ νλμ λ Ήννλ λꡬλ₯Ό κ°λ°ν©λλ€.
μμ : κ°λ¨ν λΉλμ€ λ Ήν μ ν리μΌμ΄μ ꡬννκΈ°
HTML, CSS, JavaScriptλ₯Ό μ¬μ©νμ¬ λ Όμλ κ°λ μ κΈ°λ³Έμ μΈ λΉλμ€ λ Ήν μ ν리μΌμ΄μ μ ν΅ν©νλ κ°λ¨ν μμ λ λ€μκ³Ό κ°μ΅λλ€:
HTML (index.html):
<!DOCTYPE html>
<html>
<head>
<title>Browser Video Recorder</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Browser Video Recorder</h1>
<video id="preview" autoplay muted></video><br>
<button id="recordButton">Record</button>
<button id="stopButton" disabled>Stop</button>
<script src="script.js"></script>
</body>
</html>
CSS (style.css):
body {
font-family: sans-serif;
text-align: center;
}
video {
width: 640px;
height: 480px;
border: 1px solid #ccc;
}
button {
padding: 10px 20px;
font-size: 16px;
margin: 10px;
}
JavaScript (script.js):
const preview = document.getElementById('preview');
const recordButton = document.getElementById('recordButton');
const stopButton = document.getElementById('stopButton');
let mediaRecorder;
let recordedChunks = [];
recordButton.addEventListener('click', startRecording);
stopButton.addEventListener('click', stopRecording);
async function startRecording() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
preview.srcObject = stream;
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.onstop = handleStop;
mediaRecorder.start();
recordButton.disabled = true;
stopButton.disabled = false;
} catch (err) {
console.error("Error accessing media devices.", err);
}
}
function handleDataAvailable(event) {
if (event.data.size > 0) {
recordedChunks.push(event.data);
}
}
function stopRecording() {
mediaRecorder.stop();
recordButton.disabled = false;
stopButton.disabled = true;
//Stop all video streams
preview.srcObject.getVideoTracks().forEach(track => track.stop());
}
function handleStop() {
const blob = new Blob(recordedChunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = 'recorded-video.webm';
document.body.appendChild(a);
a.click();
setTimeout(() => {
URL.revokeObjectURL(url);
document.body.removeChild(a);
}, 100);
recordedChunks = []; // Reset array for the next recording
}
μ΄ μμ λ λΈλΌμ°μ λ΄μμ μ§μ λΉλμ€λ₯Ό μΊ‘μ², νμ, λ Ήν λ° λ€μ΄λ‘λνλ ν΅μ¬ μ리λ₯Ό 보μ¬μ€λλ€. κΈ°λ₯μ ν₯μμν€κΈ° μν΄ μ€λ₯ μ²λ¦¬, λ€μν μ½λ± μ΅μ λλ μ¬μ©μκ° μ‘°μ ν μ μλ λ Ήν νμ§μ μΆκ°νλ κ²μ κ³ λ €ν΄ λ³΄μΈμ.
보μ κ³ λ €μ¬ν
MediaStream λ Ήν μμ μ ν λλ 보μ κ³ λ €μ¬νμ μΈμ§νλ κ²μ΄ μ€μν©λλ€:
- μ¬μ©μ κΆν: λ§μ΄ν¬λ μΉ΄λ©λΌμ μ κ·ΌνκΈ° μ μ νμ μ¬μ©μ κΆνμ μμ²νμμμ€. μ΄λ¬ν μ₯μΉμ μ κ·Όν΄μΌ νλ μ΄μ λ₯Ό λͺ ννκ² νμνμμμ€.
- HTTPS: λ―Έλμ΄ μ€νΈλ¦Όμ΄ μνΈνλκ³ λμ²μΌλ‘λΆν° 보νΈλλλ‘ HTTPSλ₯Ό μ¬μ©νμμμ€.
getUserMedia()APIλ μΌλ°μ μΌλ‘ 보μ 컨ν μ€νΈ(HTTPS)λ₯Ό μꡬν©λλ€. - λ°μ΄ν° μ μ₯: λ Ήνλ λ°μ΄ν°λ₯Ό μ μ₯νλ κ²½μ°, μμ νκ² μ μ₯λκ³ λ¬΄λ¨ μ κ·ΌμΌλ‘λΆν° 보νΈλλμ§ νμΈνμμμ€. μνΈν λ° μ κ·Ό μ μ΄ λ©μ»€λμ¦ μ¬μ©μ κ³ λ €νμμμ€. μ¬μ©μμ κ·Έλ€μ μμΉμ κ΄λ ¨λ λ°μ΄ν° κ°μΈ μ 보 λ³΄νΈ κ·μ (μ: GDPR, CCPA)μ μ€μνμμμ€.
- κ°μΈ μ 보 보νΈ: λ Ήνλ λ°μ΄ν°λ₯Ό μ΄λ»κ² μ¬μ©νλμ§μ λν΄ ν¬λͺ νκ² κ³΅κ°νμμμ€. μ¬μ©μμκ² μμ μ λ°μ΄ν°μ λν ν΅μ κΆκ³Ό μμ ν μ μλ κΈ°λ₯μ μ 곡νμμμ€.
- μ μ± μ½λ: μ¬μ©μ μμ± μ½ν μΈ λ₯Ό μ²λ¦¬ν λλ μ μ± μ½λκ° ν¬ν¨λ μ μμΌλ―λ‘ μ£Όμνμμμ€. μ¬μ΄νΈ κ° μ€ν¬λ¦½ν (XSS) 곡격μ λ°©μ§νκΈ° μν΄ λͺ¨λ μ¬μ©μ μ λ ₯μ μ ν(sanitize)νμμμ€.
μ±λ₯ μ΅μ ν
MediaStream λ Ήνλ₯Ό μ¬μ©ν λ μ΅μ μ μ±λ₯μ 보μ₯νλ €λ©΄ λ€μμ κ³ λ €νμμμ€:
- MIME νμ μ ν: λΈλΌμ°μ μμ μ§μνκ³ μ’μ μμΆλ₯ μ μ 곡νλ MIME νμ μ μ ννμμμ€.
- Timeslice κ°κ²©: λ°μ΄ν° κ°μ©μ±κ³Ό μ±λ₯μ κ· νμ λ§μΆκΈ° μν΄
timesliceκ°κ²©μ μ‘°μ νμμμ€.timesliceκ°κ²©μ΄ μ§§μμλ‘dataavailableμ΄λ²€νΈκ° λ μμ£Ό λ°μνμ§λ§, μ€λ²ν€λκ° μ¦κ°ν μλ μμ΅λλ€. - λ°μ΄ν° μ²λ¦¬: λ©λͺ¨λ¦¬ λμμ μ±λ₯ λ³λͺ© νμμ νΌνκΈ° μν΄ λ Ήνλ λ°μ΄ν°λ₯Ό ν¨μ¨μ μΌλ‘ μ²λ¦¬νμμμ€. λ²νΌλ§ λ° μ€νΈλ¦¬λ°κ³Ό κ°μ κΈ°μ μ μ¬μ©νμ¬ λλμ λ°μ΄ν°λ₯Ό μ²λ¦¬νμμμ€.
- μ¬μ©μ μΈν°νμ΄μ€: λ Ήν κ³Όμ μ λν΄ μ¬μ©μμκ² λͺ νν νΌλλ°±μ μ 곡νλ μ¬μ©μ μΈν°νμ΄μ€λ₯Ό λμμΈνμμμ€. λ Ήν νμκΈ°λ₯Ό νμνκ³ λ Ήνλ₯Ό μΌμ μ€μ§, μ¬κ° λ° μ€μ§νλ 컨νΈλ‘€μ μ 곡νμμμ€.
κ²°λ‘
νλ‘ νΈμλ MediaStream λ
Ήνλ μΉ κ°λ°μκ° λΈλΌμ°μ λ΄μμ μ§μ νλΆνκ³ μνΈμμ©μ μΈ λ―Έλμ΄ κ²½νμ λ§λ€ μ μλλ‘ ν©λλ€. MediaStreamκ³Ό MediaRecorder APIλ₯Ό μ΄ν΄ν¨μΌλ‘μ¨ κ°λ°μλ μ¨λΌμΈ νμ λ° λΉλμ€ νΈμ§ λꡬλΆν° λνν νν λ¦¬μΌ λ° κ°μ μμ€ν
μ μ΄λ₯΄κΈ°κΉμ§ κ΄λ²μν μ ν리μΌμ΄μ
μ ꡬμΆν μ μμ΅λλ€. 보μ λ° μ±λ₯ κ³ λ €μ¬νμ μ£Όμλ₯Ό κΈ°μΈμμΌλ‘μ¨ μΉ μ ν리μΌμ΄μ
μ κΈ°λ₯κ³Ό μ°Έμ¬λλ₯Ό ν₯μμν€λ κ²¬κ³ νκ³ μ¬μ©μ μΉνμ μΈ λ―Έλμ΄ λ
Ήν μ루μ
μ λ§λ€ μ μμ΅λλ€.